#include "ContextFreeGrammar.h"

#include <iterator>
#include <sstream>
#include <stdexcept>

ContextFreeGrammar::ContextFreeGrammar(std::istream& in)
{
    char line[1024];
    std::string tmp;

    this->startSymbol = nonterminal.end();

    in >> std::ws;
    while(in.good())
    {
        in.getline(line, 1024);
        std::cerr << "Processing nonterminal:" << line << std::endl ;
        std::stringstream formatter(line);

        while(formatter >> tmp)
        {
            auto newitr = this->nonterminal.insert(tmp).first;
            if(startSymbol == nonterminal.end())
                startSymbol = newitr;
        }



        // Blank line
        if(in.peek() == '\n' || in.peek() == '\r')
            break;
        in >> std::ws ;
    }
    in >> std::ws ;
    while(in.good())
    {
        in.getline(line, 1024);
        std::cerr << "Processing terminal:" << line << std::endl ;
        std::stringstream formatter(line);

        while(formatter >> tmp)
            this->terminal.insert(tmp);

        // Blank line
        if(in.peek() == '\n' || in.peek() == '\r')
            break;
        in >> std::ws ;
    }

    in >> std::ws ;
    while(in.good())
    {
        in.getline(line, 1024);
        std::cerr << "Processing:" << line << std::endl ;
        std::stringstream formatter(line);

        formatter >> tmp ;
        StringSet::const_iterator leftItr = nonterminal.find(tmp);
        if(leftItr == nonterminal.end())
            throw std::runtime_error("Nonterminal not defined");
        formatter >> tmp ;
        if(tmp != "->")
            throw std::runtime_error("Invalid production");

        ProductionList right;
        while(formatter.good())
        {
            formatter >> tmp ;
            if(tmp == "EPSILON")
                right.push_back(GetEpsilon());
            else
            {
                StringSet::const_iterator rightItr = nonterminal.find(tmp);
                if(rightItr == nonterminal.end())
                {
                    rightItr = terminal.find(tmp);
                    if(rightItr == terminal.end())
                        throw std::runtime_error("Identifier not defined");
                }
                right.push_back(rightItr);
            }
        }
        production.insert(std::make_pair(*leftItr, right));

        in >> std::ws ;
    }
}

ContextFreeGrammar::~ContextFreeGrammar()
{

}

StringSet::const_iterator ContextFreeGrammar::GetEpsilon() const
{
    return terminal.cend();
}

StringSet::const_iterator ContextFreeGrammar::GetEndOfFile() const
{
    return nonterminal.cend();
}


void ContextFreeGrammar::PrintNonterminal(std::ostream& out)
{
    std::copy(nonterminal.begin(), nonterminal.end(), std::ostream_iterator <std::string> {out, ";"});
}

void ContextFreeGrammar::PrintTerminal(std::ostream& out)
{
    std::copy(terminal.begin(), terminal.end(), std::ostream_iterator <std::string> {out, ";"});
}

void ContextFreeGrammar::PrintProduction(std::ostream& out)
{
    for(auto itr = production.begin(); itr != production.end(); ++itr)
    {
        out << itr->first << " ->";

        for(auto ritr = itr->second.begin(); ritr != itr->second.end(); ++ritr)
            out << " " << SafePrint(*ritr) ;

        out << std::endl ;
    }
}

void ContextFreeGrammar::PrintProductionRight(const ProductionList& pl, std::ostream& out) const
{
    for(auto jtr = pl.begin(); jtr != pl.end(); ++jtr)
        std::cerr << SafePrint(*jtr) << " ";
}



std::string ContextFreeGrammar::SafePrint(StringSet::const_iterator itr) const
{
    return (itr == GetEpsilon() ? "EPSILON" : (itr == GetEndOfFile() ? "$" : *itr));
}

bool ContextFreeGrammar::isNonterminal(const std::string& str) const
{
    return nonterminal.find(str) != nonterminal.end();
}

std::string ContextFreeGrammar::GenerateUniqueName(const std::string& prefix)
{
    static unsigned int counter = 0;
    std::string name = prefix + "_" + std::to_string(counter++);
    if(terminal.find(name) != terminal.end() || nonterminal.find(name) != nonterminal.end())
        throw std::runtime_error("Cannot generate an unique name for " + prefix);
    return name;
}



